/*
 * SHOPTNT 版权所有。
 * 未经许可，您不得使用此文件。
 * 官方地址：www.shoptnt.cn
 */
package cn.shoptnt.service.system.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import cn.shoptnt.client.system.SettingClient;
import cn.shoptnt.framework.ShopTntConfig;
import cn.shoptnt.framework.auth.Token;
import cn.shoptnt.framework.cache.Cache;
import cn.shoptnt.framework.context.request.ThreadContextHolder;
import cn.shoptnt.framework.database.WebPage;
import cn.shoptnt.framework.exception.ResourceNotFoundException;
import cn.shoptnt.framework.exception.ServiceException;
import cn.shoptnt.framework.redis.RedisChannel;
import cn.shoptnt.framework.security.TokenManager;
import cn.shoptnt.framework.security.message.UserDisableMsg;
import cn.shoptnt.framework.security.model.Admin;
import cn.shoptnt.framework.security.model.Role;
import cn.shoptnt.framework.util.*;
import cn.shoptnt.mapper.system.AdminUserMapper;
import cn.shoptnt.model.base.CachePrefix;
import cn.shoptnt.model.base.SettingGroup;
import cn.shoptnt.model.errorcode.SystemErrorCode;
import cn.shoptnt.model.support.LogClient;
import cn.shoptnt.model.support.validator.annotation.LogLevel;
import cn.shoptnt.model.system.dos.AdminUser;
import cn.shoptnt.model.system.dos.RoleDO;
import cn.shoptnt.model.system.dos.SystemLogs;
import cn.shoptnt.model.system.dto.AdminUserDTO;
import cn.shoptnt.model.system.vo.AccountSetting;
import cn.shoptnt.model.system.vo.AdminLoginVO;
import cn.shoptnt.model.system.vo.AdminUserVO;
import cn.shoptnt.service.system.AdminUserManager;
import cn.shoptnt.service.system.RoleManager;
import cn.shoptnt.service.system.SystemLogsManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * 平台管理员业务类
 *
 * @author zh
 * @version v7.0
 * @since v7.0.0
 * 2018-06-20 20:38:26
 */
@Service
public class AdminUserManagerImpl implements AdminUserManager {

    @Autowired
    private RoleManager roleManager;
    @Autowired
    private AdminUserMapper adminUserMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private TokenManager tokenManager;
    @Autowired
    private SettingClient settingClient;
    @Autowired
    private Cache cache;

    @Autowired
    private ShopTntConfig shoptntConfig;

    /**
     * 查询平台管理员列表
     *
     * @param page     页码
     * @param pageSize 每页数量
     * @return WebPage
     */
    @Override
    public WebPage list(long page, long pageSize, String keyword) {

        IPage<AdminUserDTO> iPage = adminUserMapper.selectPageDto(new Page<>(page, pageSize), keyword);
        return PageConvert.convert(iPage);
    }

    /**
     * 添加平台管理员
     *
     * @param adminUserVO 平台管理员
     * @return AdminUser 平台管理员
     */
    @Override
    @Transactional(value = "systemTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public AdminUser add(AdminUserVO adminUserVO) {
        //校验密码格式
        boolean bool = Pattern.matches("[a-fA-F0-9]{32}", adminUserVO.getPassword());
        if (!bool) {
            throw new ServiceException(SystemErrorCode.E917.code(), "密码格式不正确");
        }
        //校验用户名称是否重复
        QueryWrapper<AdminUser> wrapper = new QueryWrapper<>();
        wrapper.eq("username", adminUserVO.getUsername()).eq("user_state", 0);
        AdminUser user = adminUserMapper.selectOne(wrapper);

        if (user != null) {
            throw new ServiceException(SystemErrorCode.E915.code(), "管理员名称重复");
        }
        //不是超级管理员的情况下再校验权限是否存在
        if (!adminUserVO.getFounder().equals(1)) {
            RoleDO roleDO = roleManager.getModel(adminUserVO.getRoleId());
            if (roleDO == null) {
                throw new ResourceNotFoundException("所属权限不存在");
            }
        }
        //管理员信息入库
        String password = adminUserVO.getPassword();
        AdminUser adminUser = new AdminUser();
        BeanUtil.copyProperties(adminUserVO, adminUser);
        adminUser.setPassword(StringUtil.md5(password + adminUser.getUsername().toLowerCase()));
        adminUser.setDateLine(DateUtil.getDateline());
        adminUser.setUserState(0);
        adminUserMapper.insert(adminUser);

        return adminUser;
    }

    /**
     * 修改平台管理员
     *
     * @param adminUserVO 平台管理员
     * @param id          平台管理员主键
     * @return AdminUser 平台管理员
     */
    @Override
    @Transactional(value = "systemTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public AdminUser edit(AdminUserVO adminUserVO, Long id) {
        //对要修改的管理员是否存在进行校验
        AdminUser adminUser = this.getModel(id);
        if (adminUser == null) {
            throw new ResourceNotFoundException("当前管理员不存在");
        }
        //如果修改的是从超级管理员到普通管理员 需要校验此管理员是否是最后一个超级管理员
        if (adminUser.getFounder().equals(1) && !adminUserVO.getFounder().equals(1)) {

            QueryWrapper<AdminUser> wrapper = new QueryWrapper<>();
            wrapper.eq("founder", 1).eq("user_state", 0);
            List<AdminUser> adminUsers = adminUserMapper.selectList(wrapper);

            if (adminUsers.size() <= 1) {
                throw new ServiceException(SystemErrorCode.E916.code(), "必须保留一个超级管理员");
            }
        }
        if (!adminUserVO.getFounder().equals(1)) {
            RoleDO roleDO = roleManager.getModel(adminUserVO.getRoleId());
            if (roleDO == null) {
                throw new ResourceNotFoundException("所属权限不存在");
            }
        } else {
            adminUserVO.setRoleId(0L);
        }
        //管理员原密码
        String password = adminUser.getPassword();
        //对管理员是否修改密码进行校验
        if (!StringUtil.isEmpty(adminUserVO.getPassword())) {
            boolean bool = Pattern.matches("[a-fA-F0-9]{32}", adminUserVO.getPassword());
            if (!bool) {
                throw new ServiceException(SystemErrorCode.E917.code(), "密码格式不正确");
            }
            adminUserVO.setPassword(StringUtil.md5(adminUserVO.getPassword() + adminUser.getUsername().toLowerCase()));
        } else {
            adminUserVO.setPassword(password);
        }
        //修改管理员名称
        adminUserVO.setUsername(adminUser.getUsername());
        BeanUtil.copyProperties(adminUserVO, adminUser);
        adminUserMapper.updateById(adminUser);
        return adminUser;
    }


    /**
     * 删除平台管理员
     *
     * @param id 平台管理员主键
     */
    @Override
    @Transactional(value = "systemTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void delete(Long id) {
        //校验当前管理员是否存在
        AdminUser adminUser = this.getModel(id);
        if (adminUser == null) {
            throw new ResourceNotFoundException("当前管理员不存在");
        }
        //校验要删除的管理员是否是最后一个超级管理员
        QueryWrapper<AdminUser> wrapper = new QueryWrapper<>();
        wrapper.eq("founder", 1).eq("user_state", 0);
        List<AdminUser> adminUsers = adminUserMapper.selectList(wrapper);

        if (adminUsers.size() <= 1 && adminUser.getFounder().equals(1)) {
            throw new ServiceException(SystemErrorCode.E916.code(), "必须保留一个超级管理员");
        }
        // 设置管理员状态为删除
        adminUser.setUserState(-1);
        adminUserMapper.updateById(adminUser);

        //发送redis订阅消息,通知各个节点此用户已经禁用
        UserDisableMsg userDisableMsg = new UserDisableMsg(id, Role.ADMIN, UserDisableMsg.ADD);
        String msgJson = JsonUtil.objectToJson(userDisableMsg);
        redisTemplate.convertAndSend(RedisChannel.USER_DISABLE, msgJson);

    }

    /**
     * 获取平台管理员
     *
     * @param id 平台管理员主键
     * @return AdminUser  平台管理员
     */
    @Override
    public AdminUser getModel(Long id) {

        QueryWrapper<AdminUser> wrapper = new QueryWrapper<>();
        wrapper.eq("user_state", 0).eq("id", id);
        return adminUserMapper.selectOne(wrapper);
    }

    @Autowired
    private SystemLogsManager systemLogsManager;


    /**
     * 管理员登录
     *
     * @param name     管理员名称
     * @param password 管理员密码
     * @return
     */
    @Override
    public AdminLoginVO login(String name, String password) {
        //查询管理员信息
        QueryWrapper<AdminUser> wrapper = new QueryWrapper<>();
        wrapper.eq("username", name).eq("user_state", 0);
        AdminUser adminUser = adminUserMapper.selectOne(wrapper);
        //管理员信息不存在
        if (adminUser == null || !StringUtil.equals(adminUser.getUsername(), name)) {
            throw new ServiceException(SystemErrorCode.E918.code(), "管理员账号密码错误");
        }
        String accountSettingJson = settingClient.get(SettingGroup.ACCOUNT);
        AccountSetting accountSetting = JsonUtil.jsonToObject(accountSettingJson, AccountSetting.class);
        checkAccountStatus(adminUser.getId(), accountSetting);
        if (!StringUtil.md5(password + name.toLowerCase()).equals(adminUser.getPassword())) {
            int times = recordPsswordErrorCount(adminUser.getId(), accountSetting);
            throw new ServiceException(SystemErrorCode.E918.code(), "管理员账号密码错误" + times + "次,超过" + accountSetting.getRetryTimes() + "次将被锁定");
        }
        //返回管理员信息
        AdminLoginVO adminLoginVO = new AdminLoginVO();
        adminLoginVO.setUid(adminUser.getId());
        adminLoginVO.setUsername(name);
        adminLoginVO.setDepartment(adminUser.getDepartment());
        adminLoginVO.setFace(adminUser.getFace());
        adminLoginVO.setRoleId(adminUser.getRoleId());
        adminLoginVO.setFounder(adminUser.getFounder());
        // 设置访问token的失效时间维持管理员在线状态
        Token token = createToken(adminUser);

        String accessToken = token.getAccessToken();
        String refreshToken = token.getRefreshToken();

        adminLoginVO.setAccessToken(accessToken);
        adminLoginVO.setRefreshToken(refreshToken);

        //记录登录日志
        addLoginLog(adminUser, name, password);

        return adminLoginVO;
    }


    /**
     * 检测账户状态
     *
     * @param adminId
     * @param accountSetting
     */
    private void checkAccountStatus(Long adminId, AccountSetting accountSetting) {
        Object lockFlag = cache.get(CachePrefix.LOCK_MANAGER.getPrefix() + adminId);
        if (lockFlag != null) {
            long endTime = (long) lockFlag;
            int minute = (int) CurrencyUtil.div(CurrencyUtil.sub(endTime, DateUtil.getDateline()), 60);
            minute = minute < 0 ? 1 : minute + 1;
            throw new ServiceException("500", "密码错误超过" + accountSetting.getRetryTimes() + "次，您的账号已经被锁定，请" + minute + "分钟后再试");
        }
    }

    /**
     * 记录密码错误次数,错误次数达到配置标准后设置账户锁定时间
     *
     * @param adminId
     * @param accountSetting
     */
    private int recordPsswordErrorCount(Long adminId, AccountSetting accountSetting) {
        String errorPwdKey = CachePrefix.PASSWORD_ERROR_NUM_MANAGER.getPrefix() + adminId;
        Object errorNum = cache.get(errorPwdKey);
        int times = 1;
        if (errorNum == null) {
            //首次错误，设置错误计数器
            int exp = CurrencyUtil.mul(accountSetting.getRetryCycle(), 3600).intValue();
            cache.put(errorPwdKey, times, exp);
        } else {
            times = (int) errorNum + 1;
            if (times >= accountSetting.getRetryTimes()) {
                //锁定账号
                lockManager(adminId, CurrencyUtil.mul(accountSetting.getLockDuration(), 60).intValue());
                //删除密码错误次数记录
                cache.remove(errorPwdKey);
                throw new ServiceException("500", "密码错误超过" + accountSetting.getRetryTimes() + "次，您的账号已经被锁定，请" + accountSetting.getLockDuration() + "分钟后再试");
            } else {
                //增加密码错误次数
                cache.put(errorPwdKey, times);
            }
        }
        return times;
    }

    /**
     * 锁定管理员账号
     *
     * @param adminId 管理员id
     * @param exp     过期时间
     */
    private void lockManager(Long adminId, int exp) {
        long endTime = CurrencyUtil.add(DateUtil.getDateline(), exp).longValue();
        cache.put(CachePrefix.LOCK_MANAGER.getPrefix() + adminId, endTime, exp);

        AdminUser adminUser = this.adminUserMapper.selectById(adminId);
        //记录登录日志
        SystemLogs systemLogs = new SystemLogs();
        systemLogs.setOperateDetail("管理员" + adminUser.getUsername() + "[" + adminUser.getRealName() + "]登录密码错误过多，被锁定");
        systemLogs.setOperateIp(StringUtil.getIpAddress());
        systemLogs.setParams("lock");
        systemLogs.setMethod("lock");
        systemLogs.setOperateTime(DateUtil.getDateline());
        systemLogs.setOperatorId(adminUser.getId());
        systemLogs.setOperatorName(adminUser.getUsername() + "[" + adminUser.getRealName() + "]");
        systemLogs.setSellerId(0L);
        systemLogs.setLevel(LogLevel.important.name());
        systemLogs.setClient("admin");
        systemLogsManager.add(systemLogs);

    }

    /**
     * 通过refreshToken重新获取accessToken
     *
     * @param refreshToken
     * @return
     */
    @Override
    public String exchangeToken(String refreshToken) {
        if (refreshToken != null) {
            Admin admin = tokenManager.parse(Admin.class, refreshToken);

            Long uid = admin.getUid();

            //获取uuid
            String uuid = ThreadContextHolder.getHttpRequest().getHeader("uuid");
            //根据id获取管理员 校验当前管理员是否存在
            AdminUser adminUser = this.getModel(uid);
            if (adminUser == null) {
                throw new ResourceNotFoundException("当前管理员不存在");
            }
            //重新获取token
            Token token = createToken(adminUser);

            String newAccessToken = token.getAccessToken();
            String newRefreshToken = token.getRefreshToken();

            Map map = new HashMap(16);
            map.put("accessToken", newAccessToken);
            map.put("refreshToken", newRefreshToken);

            return JsonUtil.objectToJson(map);

        }
        throw new ResourceNotFoundException("当前管理员不存在");
    }


    /**
     * 使用adminUser 创建token
     *
     * @param adminUser
     * @return
     */
    private Token createToken(AdminUser adminUser) {
        Admin admin = new Admin();
        admin.setUid(adminUser.getId());
        admin.setUsername(adminUser.getUsername());
        admin.setFounder(adminUser.getFounder());
        //设置管理端的uuid
        String uuid = ThreadContextHolder.getHttpRequest().getHeader("uuid");
        admin.setUuid(uuid);

        if (adminUser.getFounder().equals(1)) {
            admin.add("SUPER_ADMIN");
        } else {
            RoleDO roleDO = this.roleManager.getModel(adminUser.getRoleId());
            admin.add(roleDO.getRoleName());
        }

        return tokenManager.create(admin);

    }

    /**
     * 管理员注销登录
     *
     * @param uid 会员id
     */
    @Override
    public void logout(Long uid) {

    }

    /**
     * 记录登录日志
     *
     * @param adminUser
     * @param name
     * @param password
     */
    private void addLoginLog(AdminUser adminUser, String name, String password) {
        SystemLogs systemLogs = new SystemLogs();
        systemLogs.setOperateDetail(ObjectUtil.isNotEmpty(adminUser.getRealName()) ? adminUser.getUsername()+"["+adminUser.getRealName()+"]登录成功" : adminUser.getUsername()+"登录成功");
        systemLogs.setOperateIp(StringUtil.getIpAddress());
        systemLogs.setParams("name=" + name + "&password=" + password);
        systemLogs.setMethod("login");
        systemLogs.setOperateTime(DateUtil.getDateline());
        systemLogs.setOperatorId(adminUser.getId());
        systemLogs.setOperatorName(ObjectUtil.isNotEmpty(adminUser.getRealName()) ? adminUser.getUsername()+"["+adminUser.getRealName()+"]" : adminUser.getUsername());
        systemLogs.setSellerId(0L);
        systemLogs.setLevel(LogLevel.important.name());
        systemLogs.setClient(LogClient.admin.name());
        systemLogsManager.add(systemLogs);
    }
}
